﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace SerializerBenchmark
{
    public class POCOEntity
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public String Name1 { get; set; }
        public String Name2 { get; set; }
        public String Name3 { get; set; }
        public String Name4 { get; set; }
        public String Name5 { get; set; }
        public String Name6 { get; set; }
        public String Name7 { get; set; }
        public String Name8 { get; set; }
        public String Name9 { get; set; }
        public String Name10 { get; set; }
        public String Name11 { get; set; }
        public String Name12 { get; set; }
        public String Name13 { get; set; }
        public String Name14 { get; set; }
        public String Name15 { get; set; }
        public String Name16 { get; set; }
        public String Name17 { get; set; }
        public String Name18 { get; set; }
        public String Name19 { get; set; }
        public String Name20 { get; set; }
        public String Name21 { get; set; }
        public String Name22 { get; set; }
        public String Name23 { get; set; }
        public String Name24 { get; set; }
        public String Name25 { get; set; }
        public String Name26 { get; set; }
        public String Name27 { get; set; }
        public String Name28 { get; set; }
        public String Name29 { get; set; }
        public String Name30 { get; set; }
        public String Name31 { get; set; }
        public String Name32 { get; set; }
        public String Name33 { get; set; }
        public String Name34 { get; set; }
        public String Name35 { get; set; }
        public String Name36 { get; set; }
        public String Name37 { get; set; }
        public String Name38 { get; set; }
        public String Name39 { get; set; }
        public String Name40 { get; set; }
        public String Name41 { get; set; }
        public String Name42 { get; set; }
        public String Name43 { get; set; }
        public String Name44 { get; set; }
        public String Name45 { get; set; }
        public String Name46 { get; set; }
        public String Name47 { get; set; }
        public String Name48 { get; set; }
        public String Name49 { get; set; }
        public String Name50 { get; set; }
        public String Name51 { get; set; }
        public String Name52 { get; set; }
        public String Name53 { get; set; }
        public String Name54 { get; set; }
        public String Name55 { get; set; }
        public String Name56 { get; set; }
        public String Name57 { get; set; }
        public String Name58 { get; set; }
        public String Name59 { get; set; }
        public String Name60 { get; set; }
        public String Name61 { get; set; }
        public String Name62 { get; set; }
        public String Name63 { get; set; }
        public String Name64 { get; set; }
        public String Name65 { get; set; }
        public String Name66 { get; set; }
        public String Name67 { get; set; }
        public String Name68 { get; set; }
        public String Name69 { get; set; }
        public String Name70 { get; set; }
        public String Name71 { get; set; }
        public String Name72 { get; set; }
        public String Name73 { get; set; }
        public String Name74 { get; set; }
        public String Name75 { get; set; }
        public String Name76 { get; set; }
        public String Name77 { get; set; }
        public String Name78 { get; set; }
        public String Name79 { get; set; }
        public String Name80 { get; set; }
        public String Name81 { get; set; }
        public String Name82 { get; set; }
        public String Name83 { get; set; }
        public String Name84 { get; set; }
        public String Name85 { get; set; }
        public String Name86 { get; set; }
        public String Name87 { get; set; }
        public String Name88 { get; set; }
        public String Name89 { get; set; }
        public String Name90 { get; set; }
        public String Name91 { get; set; }
        public String Name92 { get; set; }
        public String Name93 { get; set; }
        public String Name94 { get; set; }
        public String Name95 { get; set; }
        public String Name96 { get; set; }
        public String Name97 { get; set; }
        public String Name98 { get; set; }
        public String Name99 { get; set; }
        public String Name100 { get; set; }

    }


    public class EntityDictMapper
    {
        private delegate T mapEntity<T>(Dictionary<string, string> dic);
        private static ConcurrentDictionary<Type, Delegate> cachedMappers = new ConcurrentDictionary<Type, Delegate>();

        private static ConcurrentDictionary<Type, Delegate> cachedMappers_Dict = new ConcurrentDictionary<Type, Delegate>();
        private delegate Dictionary<string, string> mapDictionary<T>(T entity);

        public static T MapToEntities<T>(Dictionary<string, string> dic)
        {
            // If a mapping function from dr -> T does not exist, create and cache one
            if (!cachedMappers.ContainsKey(typeof(T)))
            {
                // Our method will take in a single parameter, a DbDataReader
                Type[] methodArgs = { typeof(Dictionary<string, string>) };

                // The MapDR method will map a DbDataReader row to an instance of type T
                DynamicMethod dm = new DynamicMethod("MapDic", typeof(T), methodArgs, Assembly.GetExecutingAssembly().GetType().Module);
                ILGenerator il = dm.GetILGenerator();

                // We'll have a single local variable, the instance of T we're mapping
                il.DeclareLocal(typeof(T));

                // Create a new instance of T and save it as variable 0
                il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
                il.Emit(OpCodes.Stloc_0);

                foreach (PropertyInfo pi in typeof(T).GetProperties())
                {
                    // Load the T instance, SqlDataReader parameter and the field name onto the stack
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldstr, pi.Name);

                    // Push the column value onto the stack
                    il.Emit(OpCodes.Callvirt, typeof(Dictionary<string, string>).GetMethod("get_Item", new Type[] { typeof(string) }));

                    // Depending on the type of the property, convert the datareader column value to the type
                    switch (pi.PropertyType.Name)
                    {
                        case "Int16":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToInt16", new Type[] { typeof(object) }));
                            break;
                        case "Int32":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToInt32", new Type[] { typeof(object) }));
                            break;
                        case "Int64":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToInt64", new Type[] { typeof(object) }));
                            break;
                        case "Boolean":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToBoolean", new Type[] { typeof(object) }));
                            break;
                        case "String":
                            il.Emit(OpCodes.Callvirt, typeof(string).GetMethod("ToString", new Type[] { }));
                            break;
                        case "DateTime":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToDateTime", new Type[] { typeof(object) }));
                            break;
                        case "Decimal":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToDecimal", new Type[] { typeof(object) }));
                            break;
                        default:
                            // Don't set the field value as it's an unsupported type
                            continue;
                    }

                    // Set the T instances property value
                    il.Emit(OpCodes.Callvirt, typeof(T).GetMethod("set_" + pi.Name, new Type[] { pi.PropertyType }));
                }

                // Load the T instance onto the stack
                il.Emit(OpCodes.Ldloc_0);

                // Return
                il.Emit(OpCodes.Ret);

                // Cache the method so we won't have to create it again for the type T
                cachedMappers.TryAdd(typeof(T), dm.CreateDelegate(typeof(mapEntity<T>)));
            }

            // Get a delegate reference to the dynamic method
            mapEntity<T> invokeMapEntity = (mapEntity<T>)cachedMappers[typeof(T)];
            return invokeMapEntity(dic);
        }

        public static Dictionary<string, string> MapToDictionary<T>(T entity, params string[] properties)
        {
            // If a mapping function from T -> Dictionary<string,string> does not exist, create and cache one
            if (!cachedMappers_Dict.ContainsKey(typeof(T)))
            {
                // Our method will take in a single parameter, a POCO entity of T
                Type[] methodArgs = { typeof(T) };

                // The MapDR method will map a DbDataReader row to an instance of type T
                DynamicMethod dm = new DynamicMethod("MapPOCO", typeof(Dictionary<string, string>), methodArgs, Assembly.GetExecutingAssembly().GetType().Module);
                ILGenerator il = dm.GetILGenerator();

                // We'll have a single local variable, the instance of dictionary  we're mapping
                il.DeclareLocal(typeof(Dictionary<string, string>));

                // Create a new instance of T and save it as variable 0
                il.Emit(OpCodes.Newobj, typeof(Dictionary<string, string>).GetConstructor(Type.EmptyTypes));
                il.Emit(OpCodes.Stloc_0);

                foreach (string pname in properties)
                {
                    PropertyInfo pi = typeof(T).GetProperty(pname);
                    // Load the dictionary instance, T parameter and the field name onto the stack
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldstr, pi.Name);
                    il.Emit(OpCodes.Ldarg_0);


                    //Push the property value onto the stack 
                    il.Emit(OpCodes.Callvirt, typeof(T).GetMethod("get_" + pi.Name, new Type[] { }));
                    
                    switch (pi.PropertyType.Name)
                    {
                        case "Int16":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(Int16) }));
                            break;
                        case "Int32":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(Int32) }));
                            break;
                        case "Int64":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(Int64) }));
                            break;
                        case "Boolean":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(Boolean) }));
                            break;
                        case "String":
                            il.Emit(OpCodes.Callvirt, typeof(string).GetMethod("ToString", new Type[] { }));
                            break;
                        case "DateTime":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(DateTime) }));
                            break;
                        case "Decimal":
                            il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToString", new Type[] { typeof(Decimal) }));
                            break;
                        default:
                            // Don't set the field value as it's an unsupported type
                            continue;
                    }
                   
                    //add T instance field with value into dictionary
                    il.Emit(OpCodes.Callvirt, typeof(Dictionary<string, string>).GetMethod("Add", new Type[] { typeof(string), typeof(string) }));

                }

                // Load the dictionary instance onto the stack
                il.Emit(OpCodes.Ldloc_0);

                // Return
                il.Emit(OpCodes.Ret);
                // Cache the method so we won't have to create it again for the type T
                cachedMappers_Dict.TryAdd(typeof(T), dm.CreateDelegate(typeof(mapDictionary<T>)));
            }

            // Get a delegate reference to the dynamic method
            mapDictionary<T> invokeMapDictionary = (mapDictionary<T>)cachedMappers_Dict[typeof(T)];
            return invokeMapDictionary(entity);
        }
        public static void ClearCachedMapperMethods()
        {
            cachedMappers.Clear();
        }
    }

}
